// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import type {ComputeShaderLayout, BindingDeclaration, Binding} from '@luma.gl/core';
import {Buffer, Sampler, Texture, log} from '@luma.gl/core';
import type {WebGPUBuffer} from '../resources/webgpu-buffer';
import type {WebGPUSampler} from '../resources/webgpu-sampler';
import type {WebGPUTexture} from '../resources/webgpu-texture';

/**
 * Create a WebGPU "bind group layout" from an array of luma.gl bindings
 * @note bind groups can be automatically generated by WebGPU.
 */
export function makeBindGroupLayout(
  device: GPUDevice,
  layout: GPUBindGroupLayout,
  bindings: Binding[]
): GPUBindGroupLayout {
  throw new Error('not implemented');
  // return device.createBindGroupLayout({
  //   layout,
  //   entries: getBindGroupEntries(bindings)
  // })
}

/**
 * Create a WebGPU "bind group" from an array of luma.gl bindings
 */
export function getBindGroup(
  device: GPUDevice,
  bindGroupLayout: GPUBindGroupLayout,
  shaderLayout: ComputeShaderLayout,
  bindings: Record<string, Binding>
): GPUBindGroup {
  const entries = getBindGroupEntries(bindings, shaderLayout);
  device.pushErrorScope('validation');
  const bindGroup = device.createBindGroup({
    layout: bindGroupLayout,
    entries
  });
  device.popErrorScope().then((error: GPUError | null) => {
    if (error) {
      log.error(`bindGroup creation: ${error.message}`, bindGroup)();
    }
  });
  return bindGroup;
}

export function getShaderLayoutBinding(
  shaderLayout: ComputeShaderLayout,
  bindingName: string,
  options?: {ignoreWarnings?: boolean}
): BindingDeclaration | null {
  const bindingLayout = shaderLayout.bindings.find(
    binding =>
      binding.name === bindingName ||
      `${binding.name.toLocaleLowerCase()}uniforms` === bindingName.toLocaleLowerCase()
  );
  if (!bindingLayout && !options?.ignoreWarnings) {
    log.warn(`Binding ${bindingName} not set: Not found in shader layout.`)();
  }
  return bindingLayout || null;
}

/**
 * @param bindings
 * @returns
 */
function getBindGroupEntries(
  bindings: Record<string, Binding>,
  shaderLayout: ComputeShaderLayout
): GPUBindGroupEntry[] {
  const entries: GPUBindGroupEntry[] = [];

  for (const [bindingName, value] of Object.entries(bindings)) {
    let bindingLayout = getShaderLayoutBinding(shaderLayout, bindingName);
    if (bindingLayout) {
      const entry = getBindGroupEntry(value, bindingLayout.location);
      if (entry) {
        entries.push(entry);
      }
    }

    // TODO - hack to automatically bind samplers to supplied texture default samplers
    if (value instanceof Texture) {
      bindingLayout = getShaderLayoutBinding(shaderLayout, `${bindingName}Sampler`, {
        ignoreWarnings: true
      });
      if (bindingLayout) {
        const entry = getBindGroupEntry(value, bindingLayout.location, {sampler: true});
        if (entry) {
          entries.push(entry);
        }
      }
    }
  }

  return entries;
}

function getBindGroupEntry(
  binding: Binding,
  index: number,
  options?: {sampler?: boolean}
): GPUBindGroupEntry | null {
  if (binding instanceof Buffer) {
    return {
      binding: index,
      resource: {
        buffer: (binding as WebGPUBuffer).handle
      }
    };
  }
  if (binding instanceof Sampler) {
    return {
      binding: index,
      resource: (binding as WebGPUSampler).handle
    };
  }
  if (binding instanceof Texture) {
    if (options?.sampler) {
      return {
        binding: index,
        resource: (binding as WebGPUTexture).sampler.handle
      };
    }
    return {
      binding: index,
      resource: (binding as WebGPUTexture).view.handle
    };
  }
  log.warn(`invalid binding ${name}`, binding);
  return null;
}
